package com.linsen.core.redis.aspect;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.linsen.core.common.aspect.BaseAspect;
import com.linsen.core.common.constant.AuthConstant;
import com.linsen.core.common.constant.SystemConstant;
import com.linsen.core.common.exception.LimitException;
import com.linsen.core.common.model.LimitMeta;
import com.linsen.core.redis.annotation.Limit;
import com.linsen.core.redis.util.RedisUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;

import javax.servlet.http.HttpServletRequest;

/**
 * @author zhaoyong
 * @date 2023/3/28
 */
@Aspect
@Slf4j
@RequiredArgsConstructor
@Order(Ordered.HIGHEST_PRECEDENCE / 2)
public class RequestLimitAspect extends BaseAspect {
    private final RedisUtil<LimitMeta> redisUtil;

    @Pointcut("@annotation(com.linsen.core.redis.annotation.Limit)")
    public void pointcut() {
    }

    @Before("pointcut()")
    public void before(JoinPoint point) {
        HttpServletRequest request = super.getRequest();
        String token = request.getHeader(AuthConstant.TOKEN_NAME);
        String ip = super.getClientIP();

        LimitMeta meta = new LimitMeta();
        meta.setMethod(request.getMethod());
        meta.setUrl(request.getRequestURI());
        meta.setIp(ip);

        if (StrUtil.isBlank(token)) {
            String keyByIp = SystemConstant.LIMIT_CACHE_PREFIX + ip;
            handleLimit(point, meta, keyByIp);
        } else {
            meta.setToken(token);
            String keyByToken = SystemConstant.LIMIT_CACHE_PREFIX + token;
            handleLimit(point, meta, keyByToken);
        }
    }

    private void handleLimit(JoinPoint point, LimitMeta meta, final String key) {
        Limit limit = super.getAnnotation(point, Limit.class);
        Boolean hasKey = this.redisUtil.getRedisTemplate().hasKey(key);
        if (BooleanUtil.isTrue(hasKey)) {
            Long expire = this.redisUtil.getRedisTemplate().getExpire(key);
            throw new LimitException("操作繁忙 请稍后重试", expire, meta);
        }

        this.redisUtil.getOpsForValue().set(key, meta, limit.timeout(), limit.unit());
    }
}
